/*
 * Copyright 2002-2019 Intel Corporation.
 * 
 * This software is provided to you as Sample Source Code as defined in the accompanying
 * End User License Agreement for the Intel(R) Software Development Products ("Agreement")
 * section 1.L.
 * 
 * This software and the related documents are provided as is, with no express or implied
 * warranties, other than those that are expressly stated in the License.
 */

/*
 * Expanded from just testing ret far to include tests for 
 * a number of other instructions with implicit registers
 */
#include <stdio.h>

typedef unsigned long long UINT64;
typedef signed long long INT64;
typedef unsigned int UINT32;
typedef unsigned short UINT16;

extern "C" int FarCallTest();
extern "C" void MaskMovQ(void *dest, UINT64 src, UINT64 mask);
extern "C" void * pushESP(void *stack);
extern "C" void * popESP(void *stack);
extern "C" void * push4ESP(void *stack);
extern "C" void * pop4ESP(void *stack);
extern "C" void * pushStarESP(void *stack);
extern "C" void * popStarESP(void *stack);
extern "C" void * pushFlags(void *stack);
extern "C" void * pushIZero(void *stack);
extern "C" void * pushWIZero(void *stack);
extern "C" void * pushIM1(void *stack);
extern "C" void * pushIs16(void *stack);
extern "C" void * pushMemAbs(void *stack);
extern "C" void * pushCS(void *stack);
extern "C" UINT16 readCS();
extern "C" void * pushFS(void *stack);
extern "C" UINT16 readFS();

/* Test maskMovQ */
static int tmq()
{
    UINT64 dest = 0;
    UINT64 mTrue= UINT64(0x80);
    UINT64 mOnes= UINT64(0xff);
    UINT64 mask = mTrue<<(7*8) | mTrue<<(5*8) | mTrue<<(3*8) | mTrue<<(1*8);
    UINT64 src  = UINT64(INT64(-1));

    MaskMovQ(&dest, src, mask);
    if (dest == (mOnes<<(7*8) | mOnes<<(5*8) | mOnes<<(3*8) | mOnes<<(1*8)))
    {
        printf ("MaskMov: OK\n");
        return 0;
    }
    else
    {
        printf ("***Fail*** MaskMov: result 0x%08x%08x\n", UINT32(dest>>32), UINT32(dest));
        return 1;
    }
}

/* test some unpleasant push and pop cases */
static int tPushPop()
{
    void * stack[3];
    void ** stackTop = &stack[2];
    void *finalAddr= pushESP(stackTop);
    int fails = 0;

    // push %esp
    if (finalAddr != &stack[1] ||
        stack[1]  != stackTop)
    {
        printf ("***Fail*** PushESP : top %p, result %p value %p\n", stackTop, finalAddr, stack[1]);
        fails++;
    }
    else
    {
        printf ("push %%esp: OK\n");
    }

    // pop %esp
    stack[0] = (void *)0x1234;
    finalAddr = popESP(&stack[0]);
    if (finalAddr != (void *)0x1234)
    {
        printf ("***Fail*** PopESP: result %p\n", finalAddr);
        fails++;
    }
    else
    {
        printf ("pop %%esp: OK\n");
    }

    // push (%esp)
    stack[0] = 0;
    stack[1] = (void *)0xdefaced0;
    stack[2] = 0;
    finalAddr = pushStarESP(&stack[1]);

    if (finalAddr != &stack[0] ||
        stack[0] != stack[1] ||
        stack[1] != (void *)0xdefaced0)
    {
        printf ("***Fail***: push (%%esp)\n");
        fails++;
    }
    else
    {
        printf ("push (%%esp): OK\n");
    }

    // pop (%esp)
    stack[0] = (void *)0xfaded;
    stack[2] = 0;
    finalAddr = popStarESP(&stack[0]);

    if (finalAddr != &stack[1] ||
        stack[0] != stack[1] ||
        stack[1] != (void *)0xfaded ||
        stack[2] != 0)
    {
        printf ("***Fail***: pop (%%esp)\n");
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
        printf ("stack[0]  : %p should be 0xfaded\n", stack[0]);
        printf ("stack[1]  : %p should be 0xfaded\n", stack[1]);
        printf ("stack[2]  : 0x%08x should be 0\n", UINT32(stack[2]));
        fails++;
    }
    else
    {
        printf ("pop (%%esp): OK\n");
    }

    // push 4(%esp)
    stack[0] = 0;
    stack[1] = 0;
    stack[2] = (void *)0xdefaced0;
    finalAddr = push4ESP(&stack[1]);

    if (finalAddr != &stack[0] ||
        stack[0] != stack[2] ||
        stack[2] != (void *)0xdefaced0)
    {
        printf ("***Fail***: push 4(%%esp)\n");
        fails++;
    }
    else
    {
        printf ("push 4(%%esp): OK\n");
    }

    // pop 4(%esp)
    stack[0] = (void *)-1;
    stack[1] = (void *)0xfaded;
    stack[2] = 0;
    finalAddr = pop4ESP(&stack[0]);

    if (finalAddr != &stack[1] ||
        stack[0] != (void *)-1 ||
        stack[1] != (void *)0xfaded ||
        stack[2] != stack[0])
    {
        printf ("***Fail***: pop 4(%%esp)\n");
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
        printf ("stack[0]  : %p should be -1\n", stack[0]);
        printf ("stack[1]  : %p should be 0xfaded\n", stack[1]);
        printf ("stack[2]  : 0x%08x should be -1\n", UINT32(stack[2]));
        fails++;
    }
    else
    {
        printf ("pop (%%esp): OK\n");
    }


    finalAddr = pushFlags(&stack[1]);
    if (finalAddr != &stack[0])
    {
        printf ("***Fail***: pushFlags wrong ESP\n");
        fails++;
    }
    
    printf ("Flags pushed : 0x%08x\n", UINT32(stack[0]) & ~4); 

    stack[0] = stack[1] = stack[2] = (void *)-1;
    finalAddr = pushIZero(&stack[2]);
    if (finalAddr != &stack[1] ||
        stack[1] != 0)
    {
        printf ("***Fail***: pushl $0\n");
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
        printf ("stack[1]  : %p should be 0\n", stack[1]);
        fails++;
    }
    else
        printf ("pushl $0: OK\n");

    stack[0] = stack[1] = stack[2] = (void *)-1;
    finalAddr = pushWIZero(&stack[2]);
    if (finalAddr != (((char *)&stack[1])+2) ||
        stack[1] != (void *)0xffff)
    {
        printf ("***Fail***: pushw $0\n");
        printf ("FinalAddr : %p should be %p\n", finalAddr, ((char *)&stack[1])+2);
        printf ("stack[1]  : %p should be 0xffff\n", stack[1]);
        fails++;
    }
    else
        printf ("pushw $0: OK\n");

    stack[0] = stack[1] = stack[2] = (void *)0;
    finalAddr = pushIM1(&stack[2]);
    if (finalAddr != &stack[1] ||
        stack[1] != (void *)-1)
    {
        printf ("***Fail***: pushl $-1\n");
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
        printf ("stack[1]  : %p should be -1\n", stack[1]);
        fails++;
    }
    else
        printf ("pushl $-1: OK\n");

    stack[0] = stack[1] = stack[2] = (void *)0;
    finalAddr = pushIs16(&stack[2]);
    if (finalAddr != &stack[1] ||
        stack[1] != (void *)0xffff8000)
    {
        printf ("***Fail***: pushl $0xffff8000\n");
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
        printf ("stack[1]  : %p should be 0xffff8000\n", stack[1]);
        fails++;
    }
    else
        printf ("pushl $0xffff8000: OK\n");

    stack[0] = stack[1] = stack[2] = (void *)-1;
    finalAddr = pushMemAbs(&stack[2]);
    if (finalAddr != &stack[1] ||
        stack[1] != (void *)0x01234567)
    {
        printf ("***Fail***: pushl absoluteAddress\n");
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
        printf ("stack[1]  : %p should be 0x01234567\n", stack[1]);
        fails++;
    }
    else
        printf ("pushl absoluteAddress: OK\n");

    printf ("%%cs = 0x%04x\n", readCS());
    stack[0] = stack[1] = stack[2] = (void *)-1;
    finalAddr = pushCS(&stack[2]);
    if (finalAddr != &stack[1])
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
    printf ("After push %%cs, value pushed is %p\n", stack[1]);

    stack[0] = stack[1] = stack[2] = (void *)0xaaaaaaaa;
    finalAddr = pushCS(&stack[2]);
    if (finalAddr != &stack[1])
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
    printf ("After push %%cs, value pushed is %p\n", stack[1]);
    
    printf ("%%fs = 0x%04x\n", readFS());
    stack[0] = stack[1] = stack[2] = (void *)0xaaaaaaaa;
    finalAddr = pushFS(&stack[2]);
    if (finalAddr != &stack[1])
        printf ("FinalAddr : %p should be %p\n", finalAddr, &stack[1]);
    printf ("After push %%fs, value pushed is 0x%04x\n", UINT32(stack[1]) & 0x0000ffff);

    return fails;
}

int main()
{
    int res = FarCallTest();
    int fails = res != 5;

    printf("Res is 0x%x\n", res);

    fails += tmq();
    fails += tPushPop();

    return fails;
}

